module.exports = {


  friendlyName: 'Update priority vulnerabilities',


  description: 'Sets vulnerabilities to be priority and returns the patch progress of all priority vulnerabilities.',


  inputs: {
    newPriorityCveIds: {
      type: ['string'],
      description: 'An array containing strings of CVE IDs of vulnerabilities that will be tracked on the dashboard page.'// TODO: better description
    }
  },


  exits: {
    success: {
      outputType: [{}],
    },

    invalidCve: {
      responseType: 'badRequest',
      description: 'No matching cve could be found'
    },
  },


  fn: async function ({newPriorityCveIds}) {


    // If the form was submitted without any priority CVEs then we'll clear out any that exist
    if(!newPriorityCveIds){
      await Vulnerability.update({isPriority: true}).set({isPriority: false});
      return [];
    }

    let platformRecord = await Platform.find({}).limit(1);

    // Check each CVE ID to make sure they are valid.
    let cveIdsThatExistInTheDatabase = [];
    let cveIdsNotFoundInDatabase = [];
    let invalidCves = [];

    for(let newCve of newPriorityCveIds) {
      // Trim whitespace from the CVE ID and make sure it is upper case. https://github.com/fleetdm/fleet/issues/14904
      let trimmedUpperCaseCveId = _.trim(newCve.toUpperCase());
      // Check the CVE ID provided to see if it is valid.
      let newCveIsInTheExpectedFormat = !! trimmedUpperCaseCveId.match(/^CVE-\d{4}-\d+$/gi);
      let vulnExists = await Vulnerability.findOne({cveId: trimmedUpperCaseCveId});
      if(vulnExists){// If we found a matching Vulnerability record in the database, we'll add it to the array of existing vulnerabilities
        cveIdsThatExistInTheDatabase.push(trimmedUpperCaseCveId);
      } else if(newCveIsInTheExpectedFormat){// If the CVE ID did not match a Vulnerability record, we'll add it to the array of CVE IDs not found in the database.
        cveIdsNotFoundInDatabase.push(trimmedUpperCaseCveId);
      } else if (!newCveIsInTheExpectedFormat){// If the CVE ID is invalid (does not follow the standard CVE-xxxx-xxxx format), we'll add the original provided cve id to the array of invalidCves.
        invalidCves.push(newCve);
      }
    }
    if(invalidCves.length > 0) {
      // If an invalid CVE ID was provided, return an 'invalidCve' response.
      throw {invalidCve: invalidCves};// Note: we're passing in the array of invalid CVE IDs so we can display them in an error message.
    }


    let existingPriorityCves = await Vulnerability.find({isPriority: true});
    // Get a filtered array of CVE IDs that are no longer a priority.
    let previousPriorityCveIds = _.pluck(existingPriorityCves, 'cveId');
    // Find priority CVEs that have been removed from the list
    let cvesThatAreNoLongerAPriority = _.difference(previousPriorityCveIds, newPriorityCveIds);
    // update the vulnerabilities that we are no longer tracking
    await Vulnerability.update({cveId: {in: cvesThatAreNoLongerAPriority}}).set({isPriority: false});

    let vulnsToCheckPatchProgressFor = [];
    // Update the vulnerability record for our new priority cves
    for(let cve of cveIdsThatExistInTheDatabase){
      let vuln = await Vulnerability.updateOne({cveId: cve}).set({isPriority: true});
      // Add the updated record to the array of vulnerabilities we'll be checking patch progress for.
      vulnsToCheckPatchProgressFor.push(vuln);
    }//∞

    let priorityVulnPatchProgress = [];
    // Get patch progress for vulnerabilities that have been reported by the Fleet instance.
    for(let vuln of vulnsToCheckPatchProgressFor) {
      let vulnPatchProgress = _.clone(vuln);
      vulnPatchProgress.affectedSoftware = [];
      // Calculate how many host have been affected by this vulnerability, and how many hosts are currently affected by this vulnerability
      let installsForThisVulnerability = await VulnerabilityInstall.find({vulnerability: vuln.id});
      // This number will represent the number of hosts that are currently affected by the vulnerability
      let uniqueAffectedHosts = _.uniq(_.pluck(installsForThisVulnerability, 'host'));
      // Get a list of software that is currently installed and affected by this vulnerability.
      let unresolvedInstallsForThisVuln = _.filter(installsForThisVulnerability, {uninstalledAt: 0});
      let unresolvedHosts = _.uniq(_.pluck(unresolvedInstallsForThisVuln, 'host'));
      let resolvedHosts = _.difference(uniqueAffectedHosts, unresolvedHosts);
      let uniqNumberOfResolvedInstallsForThisVuln = resolvedHosts.length;
      // Iterate through the installs for this vulnerability to build a list of software
      await sails.helpers.flow.simultaneouslyForEach(_.uniq(installsForThisVulnerability, 'fleetApid'), (install)=>{
        vulnPatchProgress.affectedSoftware.push({name: install.softwareName, version: install.versionName, url: sails.config.custom.fleetBaseUrl+'/software/'+install.fleetApid });
      });
      // Get the number of unique hosts who were previosuly affected by this vulnerability.
      vulnPatchProgress.numberOfHostsAffected = uniqueAffectedHosts.length;
      // To calculate the patch progress, we'll use the number of unique hosts who were previously affected by this vulnerability as the numerator and the number of unique hosts affected by the vulnerability as the denominator.
      vulnPatchProgress.patchProgressPercentage = Math.floor((uniqNumberOfResolvedInstallsForThisVuln / vulnPatchProgress.numberOfHostsAffected) * 100);
      priorityVulnPatchProgress.push(vulnPatchProgress);
    }//∞

    // After we get the patch progress for vulnerabilities we have records for, add patch progress for the CVEs  that have not been reported by the Fleet instance.
    for(let cveId of cveIdsNotFoundInDatabase) {
      let patchProgress = {
        cveId: cveId,
        patchProgressPercentage: 100,// Since this vulnerability is not currently affecting any hosts, we'll display 100% for its patch progress.
        additionalDetailsUrl: 'https://nvd.nist.gov/vuln/detail/'+ encodeURIComponent(cveId),
      };
      priorityVulnPatchProgress.push(patchProgress);
    }
    // Get an array of the new CVE IDs we'll be tracking as priority.
    let updatedPatchProgressCveIds = _.pluck(priorityVulnPatchProgress, 'cveId');
    // Update the platform record with the new array of priority CVE IDs.
    await Platform.updateOne({id: platformRecord[0].id}).set({priorityCveIds: updatedPatchProgressCveIds});
    // Deduplicate and sort the array of patch progress.
    priorityVulnPatchProgress = _.sortBy(_.uniq(priorityVulnPatchProgress, 'cveId'), 'cveId');

    // All done.
    return priorityVulnPatchProgress;

  }


};
